Source code for qtealeaves.observables.weighted_sum

# This code is part of qtealeaves.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""
Observable to measure the weighted sum of tensor product observables
"""

import warnings

import numpy as np

from qtealeaves.mpos import ITPO, DenseMPO, DenseMPOList, MPOSite
from qtealeaves.tooling import QTeaLeavesError

from .tensor_product import TNObsTensorProduct
from .tnobase import _TNObsBase

__all__ = ["TNObsWeightedSum"]


[docs] class TNObsWeightedSum(_TNObsBase): r""" Class to measure observables which is the weighted sum of tensor product, which means of the type .. math:: O = \sum_{i=0}^m \alpha_i\left( o_1^i\otimes o_2^i \otimes \dots \otimes o_n^i \right) where :math:`m` is the number of addends and :math:`n` the number of sites. For further informations about the single observable :math:`O_i=o_1^i\otimes o_2^i \otimes \dots \otimes o_n^i` see the documentation of :class:`TNObsTensorProduct`. The output of the measurement will be a dictionary where: - The key is the `name` of the observable - The value is its expectation value An example of this observable are Pauli decompositions of Hamiltonian, i.e. Hamiltonians written as a weighted sum of tensor product operators formed by Pauli matrices. They are usually used in the Quantum chemistry applications, such as the Variational Quantum Eigensolver. Parameters ---------- name: str Name to identify the observable tp_operators: :class:`TNObsTensorProduct` Tensor product observables. Its length, i.e. the number of tensor product observables contained in it, should be the same of the number of complex coefficients. coeffs: list of complex Coefficients of the weighted sum for each tp_operators use_itpo: bool, optional If True, measure using ITPO. Default to False. Consumed in python. """ def __init__(self, name, tp_operators, coeffs, use_itpo=False): if np.isscalar(coeffs): coeffs = [coeffs] self.tp_operators = [tp_operators] self.coeffs = [coeffs] self.use_itpo = use_itpo _TNObsBase.__init__(self, name) self.measurable_ansaetze = ("MPS", "TTN", "TTO", "ATTN")
[docs] @classmethod def empty(cls): """ Documentation see :func:`_TNObsBase.empty`. """ obj = cls(None, None, None) obj.name = [] obj.tp_operators = [] obj.coeffs = [] obj.use_itpo = True return obj
def __len__(self): """ Provide appropriate length method """ return len(self.name) def __iadd__(self, other): """ Documentation see :func:`_TNObsBase.__iadd__`. """ if isinstance(other, TNObsWeightedSum): self.name += other.name self.tp_operators += other.tp_operators self.coeffs += other.coeffs self.use_itpo = self.use_itpo and other.use_itpo else: raise QTeaLeavesError("__iadd__ not defined for this type.") return self
[docs] @classmethod def from_pauli_string(cls, name, pauli_string, use_itpo=False): """ Initialize the observable from a qiskit chemistry pauli string format. First, outside of the function use the WeightedPauliOperator method to_dict() and then give that dict as input to this function Parameters ---------- name: str Name of the observable pauli_string: dict Dictionary of pauli strings use_itpo: bool, optional If True, measure using ITPO. Default to False. Consumed in python. Returns ------- TNObsWeightedSum The weighted sum observable initialized from the pauli dictionary """ assert ( "paulis" in pauli_string.keys() ), "Dictionary is not in pauli string format" addends = pauli_string["paulis"] coeffs = [] tp_operators = TNObsTensorProduct.empty() # First, we look at each term in the weighted sum for term in addends: string = term["label"] coef = term["coeff"]["real"] + 1j * term["coeff"]["imag"] operators = [] sites = [] for idx, pauli in enumerate(string): if pauli != "I": operators.append(pauli) sites.append([idx]) if len(sites) == 0: operators.append("I") sites.append([0]) tp_operators += TNObsTensorProduct(string, operators, sites) coeffs += [coef] obs_wt = cls(name, tp_operators, coeffs, use_itpo) return obs_wt
[docs] def to_itpo(self, operators, tensor_backend, num_sites): """ Return an ITPO represented the weighted sum observable Parameters ---------- operators: TNOperators The operator class tensor_backend: instance of `TensorBackend` Tensor backend of the simulation num_sites: int Number of sites in the state to be measures Returns ------- ITPO The ITPO class """ dense_mpo_list = DenseMPOList() # Cycle over weighted sum observables for coeffs, tp_ops in zip(self.coeffs, self.tp_operators): if isinstance(tp_ops, list): tp_ops = tp_ops[0] # Cycle over the TPO of a single weighted sum for ops, sites, coef in zip(tp_ops.operators, tp_ops.sites, coeffs): mpo_sites_list = [] # Cycle over the different operators in a single TPO for op_ii, site_ii in zip(ops, sites): mpo_sites_list.append( MPOSite( site_ii[0], op_ii, 1.0, coef, operators=operators, params={} ) ) # iTPO has local weights, need to set to one after first term coef = 1.0 dense_mpo = DenseMPO(mpo_sites_list, tensor_backend=tensor_backend) dense_mpo_list.append(dense_mpo) if len(dense_mpo) > 0: warnings.warn("Adding length zero MPO to dense MPO list.") # Sites are not ordered and we have to make links match anyways dense_mpo_list = dense_mpo_list.sort_sites() itpo = ITPO(num_sites) itpo.add_dense_mpo_list(dense_mpo_list) itpo.set_meas_status(do_measurement=True) return itpo
[docs] def read(self, fh, **kwargs): """ Read the measurements of the correlation observable from fortran. Parameters ---------- fh : filehandle Read the information about the measurements from this filehandle. """ fh.readline() # separator is_meas = fh.readline().replace("\n", "").replace(" ", "") is_measured = is_meas == "T" for name in self.name: if is_measured: value = fh.readline().replace("\n", "").replace(" ", "") value = np.array(value.split(","), dtype=float) yield name, value[0] + 1j * value[1] else: yield name, None
[docs] def write_results(self, fh, state_ansatz, **kwargs): """ See :func:`_TNObsBase.write_results`. """ is_measured = self.check_measurable(state_ansatz) # Write separator first fh.write("-" * 20 + "tnobsweightedsum\n") # Assignment for the linter _ = fh.write("T \n") if is_measured else fh.write("F \n") if is_measured: for name_ii in self.name: fh.write(f"{np.real(self.results_buffer[name_ii])}, ") fh.write(f"{np.imag(self.results_buffer[name_ii])} \n")